package nl.scalasim
package controlactors

import scala.collection.mutable.LinearSeq
import scala.collection.mutable.HashMap
import scala.actors.Actor
import nl.scalasim.actors.traits._
import nl.scalasim.actors.Citizen

/**
 * The governor is a controller actor that manages the housing and jobs
 * of Citizens. 
 *
 * @author Yigal Duppen
 * @since Oct 2, 2010
 */

class Governor extends ComposedActor
        with StoppableActor
        with Logging
{

  private var citizensToAdd = LinearSeq[Int]()
  private val availableHousing = HashMap[HousingActor, Int]() // house -> places available
  private var joblessCitizens = LinearSeq[Citizen]()
  private val availableWorkplaces = HashMap[WorkplaceActor, Int]() // workplace -> places available
  
  private var availableHarvesters = LinearSeq[HarvestingActor]()
  private var availableProducers = LinearSeq[ProducingActor]()

  private def connectTo(producer: ProducingActor, harvester: HarvestingActor) = {
    log.info("Connecting " + harvester + " to " + producer)
    harvester !? (100, Connect(producer)) match {
      case Some(Ack) => log.info(harvester + " is now connected to " + producer)
      case None => error("Could not connect")
    }
  }

  private def workAt(citizen: SimulationActor, workplace: WorkplaceActor): WorkplaceActor = {
    log.info("Putting " + citizen + " to work at " + workplace)
    val workplaceResponse = workplace !! WorkAt(citizen, workplace)
    val citizenResponse = citizen !! WorkAt(citizen, workplace)

    workplaceResponse() match {
      case Available(n) if n > 0 => {
        availableWorkplaces(workplace) = n
      }
      case _ => {
        availableWorkplaces.remove(workplace)
      }
    }

    log.info(citizen + " now works at " + workplace)
    workplace
  }

  private def liveAt(house: HousingActor, citizen: Citizen): HousingActor = {
    log.info("Telling " + citizen + " to live at " + house)
    val housingResponse = house !! LiveAt(citizen, house)
    val citizenResponse = citizen !! LiveAt(citizen, house)

    housingResponse() match {
      case Available(n) if n > 0 => {
        availableHousing(house) = n
      }
      case _ => {
        availableHousing.remove(house)
      }
    }

    log.info(citizen + " now lives at " + house)
    house
  }

  private def matches(h: HarvestingActor, p: ProducingActor): Boolean = {
    h.goods == p.goods
  }

  def handleActorAdded: PartialFunction[Any, Unit] = {
    case ActorAdded(a, _) => {

      // when a harvester is added and a matching producer is available: send the harvester a Connect(producer) message
      if (a.isInstanceOf[HarvestingActor]) {
        log.info("New harvester available: " + a)
        val harvester = a.asInstanceOf[HarvestingActor]
        val producers = availableProducers filter { matches(harvester, _) }

        if (producers.isEmpty) {
          availableHarvesters :+= harvester
        }
        else {
          val producer = producers.head
          connectTo(producer, harvester)
          availableProducers = availableProducers filter { _ != producer }
        }
      }

      // when a producer is added and a matching harvester is available: send the harvester a Connect(producer) message
      if (a.isInstanceOf[ProducingActor]) {
        log.info("New producer available: " + a)
        val producer = a.asInstanceOf[ProducingActor]
        val harvesters = availableHarvesters filter { matches(_, producer) } 

        if (harvesters.isEmpty) {
          availableProducers :+= producer
        }
        else {
          val harvester = harvesters.head
          connectTo(producer, harvester)
          availableHarvesters = availableHarvesters filter { _ != harvester }
        }
      }

      if (a.isInstanceOf[Citizen]) {
        log.info("New citizen available: " + a)
        val citizen = a.asInstanceOf[Citizen]

        if (availableHousing.isEmpty) {
          error("Citizen added, but no housing available")
        }
        // when a citizen is added and housing is available: send the citizen and the house a LiveAt(citizen, house) message
        else {
          val house = availableHousing.iterator.next._1

          liveAt(house, citizen)
        }

        if (availableWorkplaces.isEmpty) {
          // no workplace available, this will happen a lot
          joblessCitizens :+= citizen
        }
        // when a citizen is added and a workplace is available: send the citizen and the workplace a WorkAt(citizen, workplace) message
        else {
          val workplace = availableWorkplaces.iterator.next._1
          workAt(citizen, workplace)
        }
      }

      // when a housing actor is added: add as much citizens as this house supports
      if (a.isInstanceOf[HousingActor]) {
        log.info("New house available: " + a)
        val house: HousingActor = a.asInstanceOf[HousingActor]

        citizensToAdd = citizensToAdd.padTo(house.maxResidents, 0)
        (0 to house.maxResidents - 1) foreach {
          i =>
            citizensToAdd(i) = citizensToAdd(i) + 1
        }
        availableHousing(house) = house.maxResidents
        log.info(house + " added, providing room for " + house.maxResidents + " citizens")
      }

      // when a workplace is added and a citizen is available: send the citizen and the workplace a WorkAt(citizen, workplace) message
      if (a.isInstanceOf[WorkplaceActor]) {
        log.info("New workplace available: " + a)
        val workplace: WorkplaceActor = a.asInstanceOf[WorkplaceActor]

        if (joblessCitizens.isEmpty) {
          availableWorkplaces(workplace) = workplace.maxWorkers
        }
        else {
          val citizen: SimulationActor = joblessCitizens.head
          joblessCitizens = joblessCitizens.tail

          workAt(citizen, workplace)
        }
      }

      reply(Ack)
    }
  }
  callbacks :+= handleActorAdded


  def handleAct: PartialFunction[Any, Unit] = {
    case Act if !citizensToAdd.isEmpty => {
      reply(AddCitizens(citizensToAdd.head))
      citizensToAdd = citizensToAdd.tail
    }

    case Act => {
      reply(Ack)
    }
  }
  callbacks :+= handleAct

}